<!doctype html>
<meta charset="utf8">
<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-retry">
<title>
  PaymentResponse.prototype.retry() method
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<script>
test(() => {
  assert_true(
    "retry" in PaymentResponse.prototype,
    "retry must be in prototype"
  );
  assert_true(
    PaymentResponse.prototype.retry instanceof Function,
    "retry must be a function"
  );
}, "PaymentResponse.prototype must have a retry() function (smoke test).");

function checkCompletedCantRetry(button) {
  button.disabled = true;
  promise_test(async t => {
    const { response } = await getPaymentRequestResponse();
    // sets response.[[complete]] to true.
    await response.complete("success");
    return promise_rejects_dom(
      t,
      "InvalidStateError",
      response.retry(),
      "response.[[complete]] is true, so rejects with InvalidStateError."
    );
  }, button.textContent.trim());
}

function repeatedCallsToRetry(button) {
  button.disabled = true;
  promise_test(async t => {
    const { response } = await getPaymentRequestResponse();
    const retryPromise = response.retry();
    await promise_rejects_dom(
      t,
      "InvalidStateError",
      response.retry(),
      "Calling retry() again rejects with an InvalidStateError"
    );
    await retryPromise;
    await response.complete("success");
  }, button.textContent.trim());
}

function callCompleteWhileRetrying(button) {
  button.disabled = true;
  promise_test(async t => {
    const { response } = await getPaymentRequestResponse();
    const retryPromise = response.retry();
    const completePromise1 = response.complete("success");
    const completePromise2 = response.complete("fail");
    assert_not_equals(
      completePromise1,
      completePromise2,
      "complete() must return unique promises"
    );
    await promise_rejects_dom(
      t,
      "InvalidStateError",
      completePromise1,
      "Calling complete() while retrying rejects with an InvalidStateError"
    );
    await promise_rejects_dom(
      t,
      "InvalidStateError",
      completePromise2,
      "Calling complete() while retrying rejects with an InvalidStateError"
    );
    assert_not_equals(
      completePromise1,
      completePromise2,
      "complete() must return unique promises"
    );
    await retryPromise;
    await response.complete("success");
  }, button.textContent.trim());
}

function callingRequestAbortMustNotAbort(button) {
  button.disabled = true;
  promise_test(async t => {
    const { response, request } = await getPaymentRequestResponse();
    const retryPromise = response.retry();
    await promise_rejects_dom(
      t,
      "InvalidStateError",
      request.abort(),
      "Calling request.abort() while retrying rejects with an InvalidStateError"
    );
    await retryPromise;
    await response.complete("success");
  }, button.textContent.trim());
}

function canRetryMultipleTimes(button) {
  button.disabled = true;
  promise_test(async t => {
    const { response } = await getPaymentRequestResponse();
    assert_equals(
      await response.retry(),
      undefined,
      "Expected undefined as the resolve value"
    );
    assert_equals(
      await response.retry(),
      undefined,
      "Expected undefined as the resolve value"
    );
    await response.complete("success");
    await promise_rejects_dom(
      t,
      "InvalidStateError",
      response.retry(),
      "Calling retry() after complete() rejects with a InvalidStateError"
    );
  }, button.textContent.trim());
}

function userCanAbortARetry(button) {
  button.disabled = true;
  promise_test(async t => {
    const { response } = await getPaymentRequestResponse();
    await promise_rejects_dom(
      t,
      "AbortError",
      response.retry(),
      "The user aborting a retry rejects with a AbortError"
    );
    await promise_rejects_dom(
      t,
      "InvalidStateError",
      response.retry(),
      "After the user aborts, response [[complete]] is true so retry() must reject with InvalidStateError"
    );
    await promise_rejects_dom(
      t,
      "InvalidStateError",
      response.complete("success"),
      "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError"
    );
  }, button.textContent.trim());
}

function userIsShownErrorsFields(button) {
  button.disabled = true;
  promise_test(async t => {
    const { response, request } = await getPaymentRequestResponse({ requestShipping: true });
    const retryPromise = response.retry({
      shippingAddress: { city: "Invalid city", addressLine: "Invalid address line" },
    });
    await retryPromise;
    await response.complete("success");
  }, button.textContent.trim());
}

function abortTheUpdate(button) {
  button.disabled = true;
  promise_test(async t => {
    const { response, request } = await getPaymentRequestResponse({
      requestShipping: true,
    });
    const shipOptionChangePromise = new Promise(resolve => {
      request.onshippingoptionchange = event => {
        // causes "abort the update" to run
        event.updateWith({ total: "error!" });
        resolve();
      };
    });
    const retryPromise = response.retry();
    await shipOptionChangePromise;
    await promise_rejects_js(
      t,
      TypeError,
      retryPromise,
      "retry() aborts with a TypeError, because totals' value is invalid"
    );
    await promise_rejects_dom(
      t,
      "InvalidStateError",
      response.complete("success"),
      "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError"
    );
  }, button.textContent.trim());
}

function callingRetryReturnsUniquePromise(button){
  button.disabled = true;
  promise_test(async t => {
    const { response } = await getPaymentRequestResponse();
    const retryPromise = response.retry();
    const promises = new Set([
      retryPromise,
      response.retry(),
      response.retry(),
    ]);
    assert_equals(promises.size, 3, "Must have three unique objects");
    await retryPromise;
    await response.complete();
  }, button.textContent.trim());
};


</script>
<h2>
    Manual Tests for PaymentResponse.retry() - Please run in order!
</h2>
<p>
  Click on each button in sequence from top to bottom without refreshing the page.
  Each button will bring up the Payment Request UI window.
</p>
<p>
  When presented with the payment sheet, use any credit card select to "Pay" multiple times.
</p>
<ol>
  <li>
    <button onclick="checkCompletedCantRetry(this);">
      A completed payment request cannot be retried.
    </button>
  </li>
  <li>
    <button onclick="repeatedCallsToRetry(this);">
      Calling retry() more than once yield a rejected promise, but the
      retryPromise resolves independently.
    </button>
  </li>
  <li>
    <button onclick="callCompleteWhileRetrying(this);">
      Calling complete() while a retry() is in progress results in an InvalidStateError.
    </button>
  </li>
  <li>
    <button onclick="callingRequestAbortMustNotAbort(this);">
      Trying to abort() via PaymentRequest is not possible.
    </button>
  </li>
  <li>
    <button onclick="canRetryMultipleTimes(this);">
      It's possible to retry() multiple times and eventually complete().
      After complete(), however, retry() rejects with an InvalidStateError.
    </button>
  </li>
  <li>
    <p>
      When shown the payment sheet, hit pay once, then abort retrying the payment.
    </p>
    <button onclick="userCanAbortARetry(this);">
      The user aborting retrying a payment causes the retryPromise to reject with AbortError.
      Aborting a payment is causes it complete.
    </button>
  </li>
  <li>
    <p>
      When shown the payment sheet, hit pay once. Check payment sheet for error fields.
      Then hit escape or otherwise abort the payment.
    </p>
    <button onclick="userIsShownErrorsFields(this);">
      When retrying, the user is shown error fields to fix.
    </button>
  </li>
  <li>
    <p>
      When shown the payment sheet, hit pay once.
      Then, change the shipping option.
      Select to pay again.
    </p>
    <button onclick="abortTheUpdate(this);">
      When "abort the update" occurs because of an update error,
      the `retryPromise` is rejected and response.[[complete]] becomes true.
    </button>
  </li>
  <li>
    <p>
      When shown the payment sheet, hit pay once. Then retry once.
    </p>
    <button onclick="callingRetryReturnsUniquePromise(this);">
      Calling retry() multiple times is always a new object.
    </button>
  </li>
  <li>
    <button onclick="done();">
      Done!
    </button>
  </li>
</ol>
<small>
  If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
  and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">owners</a>.
</small>
